package com.robertux.leaderboard.servlets;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import com.robertux.leaderboard.beans.JsonResponse;
import com.robertux.leaderboard.beans.entities.Usuario;
import com.robertux.leaderboard.util.MethodExecutionSecurity;
import com.robertux.leaderboard.util.MethodParamsChecker;
import com.robertux.leaderboard.util.ResourceLocator;

/**
 * Servlet implementation class BaseServlet
 */
@WebServlet(value="/BaseServlet", loadOnStartup=1)
public class BaseServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	public static final String USR_SESSION_NAME = "USR";
	public static final String LOG4J_PROPS_NAME = "log4j.properties";
	
	private Logger logger;
      
    public BaseServlet() {
        super();
    }
    
    public void init() {
    	this.configureLog4j();
    }
    
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    	this.handleHttp(req, resp, "POST");
    }
    
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    	this.handleHttp(req, resp, "GET");
    }
    
    public void handleHttp(HttpServletRequest req, HttpServletResponse resp, String httpMethod) throws ServletException, IOException {
    	this.logger.debug("Request: " + httpMethod + " " + req.getRequestURL().toString());
    	String action = getRequestAction(req);
    	
    	try {
			Method method = this.getClass().getMethod(action, HttpServletRequest.class, HttpServletResponse.class);
			JsonResponse validCode = this.isMethodValid(method, req, httpMethod);
			if(validCode.getCode() == 0) {
				String missingParam = this.getMissingParameters(method, req);
				if(missingParam.trim().length() == 0) {
					JsonResponse jsonResp = (JsonResponse)method.invoke(this, req, resp);
					this.writeResponse(resp, jsonResp);
				} else {
					this.writeResponse(resp, JsonResponse.fromError(1005, missingParam, action));
				}
			} else {
				this.writeResponse(resp, validCode);
			}
		} catch (Exception e) {
			this.logger.error("Excepción: " + e.getMessage(), e);
			if(e instanceof NoSuchMethodException) {
				this.writeResponse(resp, JsonResponse.fromError(1002, action));
			} else {
				this.writeResponse(resp, JsonResponse.fromError(1003, action));
			}
		}
    }

	protected String getRequestAction(HttpServletRequest req) {
		String action = req.getRequestURL().toString().substring(req.getRequestURL().toString().lastIndexOf("/"));
    	action = action.replace("/", "");
    	this.logger.info("Action: " + action + " Params: " + this.paramsToString(req));
		return action;
	}
    
    protected JsonResponse isMethodValid(Method method, HttpServletRequest req, String httpMethod) {
		JsonResponse isValid = JsonResponse.fromError(1004, this.getRequestAction(req));
		if(method.isAnnotationPresent(MethodExecutionSecurity.class)) {
			MethodExecutionSecurity meSec = method.getAnnotation(MethodExecutionSecurity.class);
			
			Usuario usr = (req.getSession().getAttribute(USR_SESSION_NAME) != null 
					&& req.getSession().getAttribute(USR_SESSION_NAME) instanceof Usuario? 
					(Usuario)req.getSession().getAttribute(USR_SESSION_NAME): null);
			
			try {
				this.logger.debug("=== Método: " + method.getName() + " nivel de rol requerido: " + meSec.roleLevelAllowed().getNivel() 
						+ " nivel de rol del usuario: " + usr.getRol().getNivel() + " methodo HTTP requerido: " + meSec.method() + 
						" metodo HTTP recibido: " + httpMethod);
			} catch(Exception ex) {}
			
			if(!meSec.requireAuthentication() || 
					((meSec.requireAuthentication() && usr != null) 
							&& usr.getRol().getNivel() >= meSec.roleLevelAllowed().getNivel())
							&& meSec.method().contains(httpMethod)) {
				isValid = new JsonResponse();
			}
		}
		
		return isValid;
	}
    
    protected String getMissingParameters(Method method, HttpServletRequest req) {
    	String missingParameter = "";
    	if(method.isAnnotationPresent(MethodParamsChecker.class)) {
    		MethodParamsChecker mpCheck = method.getAnnotation(MethodParamsChecker.class);

    		for(String paramName: mpCheck.requiredParams()) {
    			if(req.getParameter(paramName) == null) {
    				this.logger.warn("Falta parámetro requerido: " + paramName);
    				missingParameter = paramName;
    				break;
    			}
    		}
    	}
    	
    	return missingParameter;
    }
    
    protected String paramsToString(HttpServletRequest req) {
    	List<String> params = new ArrayList<>();
    	for(String name: req.getParameterMap().keySet()) {
    		params.add(name + ": " + 
    				(name.toUpperCase().contains("PASSW") || name.toUpperCase().contains("CONTRASE") || name.toUpperCase().contains("PWD")? 
    				"********": 
    				req.getParameter(name)));
    	}
    	return Arrays.toString(params.toArray());
    }

	protected void writeResponse(HttpServletResponse resp, String message) throws IOException {
    	resp.setCharacterEncoding("UTF-8");
    	resp.setHeader("Content-Type", "text/xml; charset=UTF-8");
    	this.logger.info("Entregando respuesta desde Servlet: " + message);
    	
    	try {
			resp.getWriter().write(message);
			resp.getWriter().flush();
		} catch (Exception e) {
			this.logger.warn("No fue posible escribir la respuesta de la llamada del servlet: " + e.getMessage());
		}
    }
    
    protected void writeResponse(HttpServletResponse resp, JsonResponse jsonResp) throws IOException {
    	resp.setContentType("application/json");
    	this.writeResponse(resp, jsonResp.toJson());
    }
    
    protected void configureLog4j() {
		try {
			Properties props = ResourceLocator.loadProperties(LOG4J_PROPS_NAME);
			PropertyConfigurator.configure(props);
			if(isLog4jConfigured()) {
				this.logger = Logger.getLogger(this.getClass());
				this.logger.info("Log4j configurado satisfactoriamente desde el archivo log4j.properties");
			} else {
				BasicConfigurator.configure();
				this.logger = Logger.getLogger(this.getClass());
				this.logger.warn("No se pudo cargar la configuración del archivo log4j.properties");
				this.logger.info("Log4j configurado de manera alternativa para su configuración básica");
			}
		} catch (Exception e) {
			this.logger.error("Excepción: " + e.getMessage(), e);
		}
    }
    
    @SuppressWarnings("unchecked")
	protected boolean isLog4jConfigured() {
        Enumeration<Object> appenders = Logger.getRootLogger().getAllAppenders();
        if (appenders.hasMoreElements()) {
            return true;
        }
        else {
            Enumeration<Object> loggers = LogManager.getCurrentLoggers() ;
            while (loggers.hasMoreElements()) {
                Logger c = (Logger) loggers.nextElement();
                if (c.getAllAppenders().hasMoreElements())
                    return true;
            }
        }
        return false;
    }
    
    protected Usuario getCurrentUsuario(HttpServletRequest req) {
    	Usuario usr = null;
    	HttpSession session = req.getSession(false);
    	if(null != session && null != session.getAttribute(USR_SESSION_NAME) 
				&&  session.getAttribute(USR_SESSION_NAME) instanceof Usuario) {
    		
    		usr = (Usuario)session.getAttribute(USR_SESSION_NAME);
    	}
    	return usr;
    }
 }
